package core

import (
	"errors"
	"fmt"
	"net"
	"os"
	"strconv"
	"strings"

	// "zstackgo/pkg/utils/path"
	"zstackgo/java"
	"zstackgo/pkg/core/cloudbus"
	"zstackgo/pkg/core/componentloader"
	"zstackgo/pkg/core/config"
	"zstackgo/pkg/core/db"
	"zstackgo/pkg/core/encrypt"
	"zstackgo/pkg/header"
	"zstackgo/pkg/header/vo"
	"zstackgo/pkg/identity/rbac"
	"zstackgo/pkg/identity/rbac/datatype"
	"zstackgo/pkg/query"
	"zstackgo/pkg/zql/ast"
	"zstackgo/pkg/zql/ast/visitors"
	"zstackgo/pkg/zql/sql"
	"zstackgo/todo"

	"github.com/go-spring/spring-core/gs"
	"github.com/meilihao/golib/v2/cmd"
	"github.com/meilihao/golib/v2/log"
	uuid "github.com/satori/go.uuid"
	"go.uber.org/zap"
	"gopkg.in/ini.v1"
)

const (
	PlatformExitReason = "zstack.quit.reason"
	PlatformSkipStop   = "skip.mn.exit"
)

var (
	bus                                    cloudbus.CloudBus
	PlatformLoader                         componentloader.ComponentLoader
	PlatformMsId                           string
	PlatformManagementServerIp             string
	PlatformManagementServerCidr           string
	PlatformMessageSource                  interface{} // MessageSource
	PlatformEncryptionKey                  string
	PlatformRsa                            *encrypt.EncryptRSA
	PlatformErrorCounter                   = map[string]float64{}
	PlatformComponentClasspathHome         = "componentsHome"
	PlatformFakeUuid                       = "THIS_IS_A_FAKE_UUID"
	PlatformGlobalProperties               = map[string]string{}
	PlatformLocale                         string
	PlatformIsRunning                      uint8       // volatile
	PlatformReflections                    interface{} // Reflections
	PlatformEncryptedMethodsMap            = map[string]func(){}
	PlatformChildResourceToBaseResourceMap = map[string]string{}
	PlatformDynamicObjectMetadata          = map[interface{}]interface{}{} //map[any]DynamicObjectMetadata{}
)

func PlatformInit() {
	var err error
	defer func() {
		if err != nil {
			log.Glog.Error("unhandled exception when in Platform's static block", zap.Error(err))

			if StaticCoreGlobalProperty.ExitJVMOnBootFailure {
				os.Exit(1)
			}
		}
	}()

	// - 将带BaseResource注解的类将其及其子类加入childResourceToBaseResourceMap

	// globalPropertiesFile, err := path.FindFileOnClassPath("zstack.properties", true)
	// if err != nil {
	// 	return
	// }
	// defer globalPropertiesFile.Close()

	java.SystemProps, err = ini.Load("zstack.properties")
	if err != nil {
		return
	}

	managementServerIp := PlatformGetManagementServerIp()
	if managementServerIp == "" {
		err = errors.New("not found managementServerIp")
		return
	}

	PlatformMsId = strings.ReplaceAll(uuid.NewV3(uuid.NamespaceDNS, managementServerIp).String(), "-", "")

	// collectDynamicObjectMetadata: 没找到实现了DynamicObject的子类
	// linkGlobalProperty: 通过注解GlobalPropertyDefinition将zstack.properties属性值设置到该类上
	// validateGlobalProperty: 通过注解GlobalProperty验证通过zstack.properties设入的值
	// prepareDefaultDbProperties:设置db属性
	// prepareHibernateSearchProperties: 设置Hibernate属性. jgroup是当前被广泛使用的可靠组间通信的工具之一, Hibernates使用了它
	CallStaticInitMethods() // 执行带StaticInit注解的方法
	// getAllEncryptPassword() // 获取带ENCRYPT注解的加密方法, 没找到
	if err = PlatformWritePidFile(); err != nil {
		return
	}
}

// order是根据`@StaticInit`的order属性决定其默认是0, 顺序是降序. 除了ZQLMetadata外其他实际都是0
// example: `search/src/main/java/org/zstack/zql/ast/ZQLMetadata.java:    @StaticInit(order = -999)`
func CallStaticInitMethods() {
	db.EntityMetadataStaticInit() // 为db model设置matadata
	sql.SQLConditionBuilderStaticInit()
	rbac.RBACStaticInit()
	db.DBGraphStaticInit()
	visitors.QueryVisitorStaticInit()
	datatype.EntityStaticInit()
	vo.ResourceTypeMetadataStaticInit()
	vo.ToInventoryInventoryMetadataStaticInit()
	visitors.SearchVisitorStaticInit()
	query.QueryHelperStaticInit()
	ast.ZQLMetadataStaticInit()
}

func PlatformCreateComponentLoaderFromWebApplicationContext(appCtx gs.Container) componentloader.ComponentLoader {
	PlatformLoader = componentloader.NewComponentLoaderImpl(appCtx)
	todo.Set.Store(todo.KeyPlatformLoader, PlatformLoader) // 打破core和componentloader的循环引用
	PlatformLoader.GetPluginRegistry()

	gcf := PlatformLoader.GetComponent("GlobalConfigFacade").(*config.GlobalConfigFacadeImpl)
	if gcf != nil {
		if err := ((header.Component)(gcf)).Start(); err != nil {
			log.Glog.Fatal("start GlobalConfigFacade", zap.Error(err))
		}
	}

	bus = PlatformLoader.GetComponent("CloudBus").(*cloudbus.CloudBusImpl3)
	if bus != nil {
		if err := bus.Start(); err != nil {
			log.Glog.Fatal("start CloudBus", zap.Error(err))
		}
	}

	return PlatformLoader
}

func GetComponentLoader() componentloader.ComponentLoader {
	if PlatformLoader == nil {
		PlatformLoader = PlatformCreateComponentLoaderFromWebApplicationContext(nil)
	}

	return PlatformLoader
}

func PlatformWritePidFile() error {
	if StaticCoreGlobalProperty.UnitTestOn {
		return nil
	}

	pidFile := StaticCoreGlobalProperty.PidFilePath
	if _, err := os.Stat(pidFile); err == nil { // exists
		pidData, _ := os.ReadFile(pidFile)
		pid, _ := strconv.ParseInt(string(pidData), 10, 64)
		if _, err := os.Stat(fmt.Sprintf("/proc/%d", pid)); err == nil { // exists
			log.Glog.Error(fmt.Sprintf("pid file[%s] exists and the process[pid:%d] that the pid file points to is still running", pidFile, pid))
			os.Exit(1)
		}

		log.Glog.Info(fmt.Sprintf("stale pid file[%s], ignore it", pidFile))
	}

	// TODO: deleteOnExit pidFile

	pid := os.Getpid()
	return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d", pid)), 0666)
}

func PlatformGetManagementServerIp() string {
	if PlatformManagementServerIp == "" {
		PlatformManagementServerIp = PlatformGetManagementServerIpInternal()
	}

	return PlatformManagementServerIp
}

func PlatformGetManagementServerIpInternal() string {
	ip := java.SystemGetProperty("management.server.ip")
	if ip != "" {
		log.Glog.Info(fmt.Sprintf("get management IP[%s] from Java property[management.server.ip]", ip))
		return ip
	}

	ip = os.Getenv("ZSTACK_MANAGEMENT_SERVER_IP")
	if ip != "" {
		log.Glog.Info(fmt.Sprintf("get management IP[%s] from environment variable[ZSTACK_MANAGEMENT_SERVER_IP]", ip))
		return ip
	}

	ret, _ := cmd.CmdCombined(&cmd.Option{IgnoreErr: true}, "ip", "route")
	var defaultLine string
	for _, s := range strings.Split(string(ret), "\n") {
		if strings.Contains(s, "default via") {
			defaultLine = s
			break
		}
	}
	if defaultLine != "" {
		ifs, err := net.Interfaces()
		if err != nil {
			log.Glog.Error("", zap.Error(err))
		}

		for _, i := range ifs {
			if strings.Contains(defaultLine, i.Name) {
				addrs, _ := i.Addrs()
				for _, addr := range addrs {
					ip = addr.String()
					break
				}
				if ip != "" {
					break
				}
			}
		}
	}

	if ip == "" {
		log.Glog.Error(`cannot get management server ip of this machine. there are three ways to get the ip.
1) search for 'management.server.ip' java property
2) search for 'ZSTACK_MANAGEMENT_SERVER_IP' environment variable
3) search for default route printed out by '/sbin/ip route'
however, all above methods failed`)

		return ip
	}

	log.Glog.Info(fmt.Sprintf("get management IP[%s] from default route[/sbin/ip route]", ip))
	return ip
}
